﻿//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Microsoft Public License.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace AstoriaOverAstoria
{
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Linq.Expressions;

    /// <summary>Expression converter for Filters (that is Where conditions).</summary>
    internal class FilterConverter : ExpressionConverter
    {
        /// <summary>Constructor</summary>
        /// <param name="annotations">The resource annotations for the expressions being processed.</param>
        /// <param name="contextMapping">The context mapping, that it the metadata for the service this expression applies to.</param>
        public FilterConverter(ExpressionResourceAnnotations annotations, MetadataMapping contextMapping)
            : base(annotations, contextMapping)
        {
        }

        /// <summary>Visits a method call expression.</summary>
        /// <param name="m">The method call to visit.</param>
        /// <returns>THe visited expression.</returns>
        internal override Expression VisitMethodCall(MethodCallExpression m)
        {
            var whereMatch = ExpressionUtil.MatchWhereCall(m);
            if (whereMatch != null)
            {
                Expression source = this.Visit(whereMatch.Source);
                LambdaExpression lambda = Expression.Lambda(
                    OptimizeFilterNullableBooleanToCondition(this.Visit(whereMatch.LambdaBody)),
                    whereMatch.Lambda.Parameters.ToArray());

                return Expression.Call(
                    whereMatch.MethodCall.Method,
                    source,
                    lambda);
            }

            return VisitMethodCallSkipProjections(m);
        }

        /// <summary>Optimizes a nullable boolean condition (quite common in filter expressions).</summary>
        /// <param name="expr">The expression to optimize.</param>
        /// <returns>The optimizes expression.</returns>
        /// <remarks>This method assumes that the returned expression will be used as the filter expression
        /// directly, it will return boolean typed expression if the optimization took place!</remarks>
        private static Expression OptimizeFilterNullableBooleanToCondition(Expression expr)
        {
            if (expr.Type == typeof(bool))
            {
                var nullEqualConditionalMatch = ExpressionUtil.MatchNullEqualConditional(expr);
                if (nullEqualConditionalMatch != null &&
                    nullEqualConditionalMatch.ComparisonOperand.Type == typeof(bool?) &&
                    ExpressionUtil.IsFalseConstant(nullEqualConditionalMatch.IfNull) &&
                    ExpressionUtil.IsMemberAccess(nullEqualConditionalMatch.IfNotNull, "Value"))
                {
                    // (c == null) ? false : c.Value    (bool?)c
                    // =>
                    // c == (bool?)true
                    return Expression.Equal(
                        nullEqualConditionalMatch.ComparisonOperand,
                        Expression.Constant(true, typeof(bool?)));
                }
            }

            return expr;
        }
    }
}
